﻿using Server.EventHandlers;
using Server.Packets;
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Server
{
    /// <summary>
    /// A fully configurable TCP server component.
    /// </summary>
    public partial class TcpServer : Component, IDisposable
    {
        #region Events

        /// <summary>
        /// Occurs when an error is raised by the <see cref="T:Server.ConnectionServer" />.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
        [Description("Occurs when an error is raised by the connection server.")]
        [Category("Connection Server")]
        public event GenericEventHandler<ConnectionServer, ExceptionEventArgs> OnConnectionServerError;

        /// <summary>
        /// Occurs when a client is connected to the <see cref="T:Server.ConnectionServer" />.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
        [Description("Occurs when a client is connected to the connection server.")]
        [Category("Connection Server")]
        public event GenericEventHandler<ConnectionServer, TcpClientEventArgs> OnConnectionServerClientConnected;

        /// <summary>
        /// Occurs when an error is raised by the <see cref="T:Server.TcpServer" />.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
        [Description("Occurs when an error is raised by the server.")]
        [Category("Server")]
        public event GenericEventHandler<TcpServer, ExceptionEventArgs> OnTcpServerError;

        /// <summary>
        /// Occurs when an error is raised by a <see cref="T:Server.TcpClientManager" />.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
        [Description("Occurs when an error is raised by a client manager.")]
        public event GenericEventHandler<TcpClientManager, ExceptionEventArgs> OnClientManagerError;

        /// <summary>
        /// Occurs when a <see cref="T:Server.TcpClientManager" /> is created by the <see cref="T:Server.TcpServer" />.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
        [Description("Occurs when a client manager is created by the server.")]
        public event GenericEventHandler<TcpServer, TcpClientManagerEventArgs> OnTcpServerClientCreated;

        /// <summary>
        /// Occurs when a <see cref="T:Server.TcpClientManager" /> is disposed by the <see cref="T:Server.TcpServer" />.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
        [Description("Occurs when a client manager is disposed by the server.")]
        public event GenericEventHandler<TcpServer, TcpClientManagerEventArgs> OnTcpServerClientDisposed;

        /// <summary>
        /// Occurs when a <see cref="T:Server.Packets.Packet" /> is received from a <see cref="T:System.Net.Sockets.TcpClient" />.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
        [Description("Occurs when a packet is is received from a client.")]
        public event GenericEventHandler<TcpClientManager, PacketEventArgs> OnPacketReceived;

        /// <summary>
        /// Occurs when a <see cref="T:Server.Packets.Packet" /> is sent to a <see cref="T:System.Net.Sockets.TcpClient" />.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]
        [Description("Occurs when a packet is is sent to a client.")]
        public event GenericEventHandler<TcpClientManager, PacketEventArgs> OnPacketSent;

        #endregion

        #region Fields

        /// <summary>
        /// A lock <see cref="T:System.Object" />.
        /// </summary>
        private readonly object m_lock;

        /// <summary>
        /// The first in-first out (FIFO) collection of <see cref="T:Server.Packets.ClientPacket" /> to send to the clients.
        /// </summary>
        private readonly ConcurrentQueue<ClientPacket> m_packetsToSend;

        /// <summary>
        /// The first in-first out (FIFO) collection of <see cref="T:Server.Packets.ClientPacket" /> received from the clients.
        /// </summary>
        private readonly ConcurrentQueue<ClientPacket> m_receivedPackets;

        /// <summary>
        /// The collection of <see cref="T:Server.TcpClientManager" /> managed by the <see cref="T:Server.TcpServer" />.
        /// </summary>
        private readonly ConcurrentDictionary<TcpClientManager, bool> m_clients;

        /// <summary>
        /// The collection of pending client addresses.
        /// </summary>
        private readonly ConcurrentBag<string> m_pendingClientsAddresses;

        /// <summary>
        /// Indicates whether the <see cref="T:Server.TcpServer" /> is started.
        /// </summary>
        private bool m_isStarted;

        /// <summary>
        /// Indicates whether the connections should be handled.
        /// </summary>
        private bool m_handleConnections;

        /// <summary>
        /// Indicates whether the disconnections should be handled.
        /// </summary>
        private bool m_handleDisonnections;

        /// <summary>
        /// Indicates whether the received <see cref="T:Server.Packets.ClientPacket" /> should be handled.
        /// </summary>
        private bool m_handleReceivedPackets;

        /// <summary>
        /// Indicates whether the <see cref="T:Server.Packets.ClientPacket" /> sending should be handled.
        /// </summary>
        private bool m_handlePacketsSending;

        /// <summary>
        /// The <see cref="T:Server.ConnectionServer" />.
        /// </summary>
        private ConnectionServer m_connectionServer;

        /// <summary>
        /// The client <see cref="T:System.Net.Sockets.TcpListener" />.
        /// </summary>
        private TcpListener m_clientListener;

        /// <summary>
        /// The connections handler <see cref="T:System.Threading.Tasks.Task" />.
        /// </summary>
        private Task m_connectionsHandlerTask;

        /// <summary>
        /// The disconnections handler <see cref="T:System.Threading.Tasks.Task" />.
        /// </summary>
        private Task m_disconnectionsHandlerTask;

        /// <summary>
        /// The receive <see cref="T:Server.Packets.Packet" /> handler <see cref="T:System.Threading.Tasks.Task" />.
        /// </summary>
        private Task m_receivePacketsHandlerTask;

        /// <summary>
        /// The send <see cref="T:Server.Packets.Packet" /> handler <see cref="T:System.Threading.Tasks.Task" />.
        /// </summary>
        private Task m_sendPacketsHandlerTask;

        /// <summary>
        /// The server parameters.
        /// </summary>
        private ServerParameters m_serverParameters;

        #endregion

        #region Properties

        /// <summary>
        /// The <see cref="T:Server.Packets.Packet" /> received from the clients.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        internal ConcurrentQueue<ClientPacket> ReceivedPackets
        {
            get
            {
                Contract.Ensures(Contract.Result<ConcurrentQueue<ClientPacket>>() != null);

                return m_receivedPackets;
            }
        }

        /// <summary>
        /// The pending clients remote Ip.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        internal ConcurrentBag<string> PendingClientsAddresses
        {
            get
            {
                Contract.Ensures(Contract.Result<ConcurrentBag<string>>() != null);

                return m_pendingClientsAddresses;
            }
        }

        /// <summary>
        /// The server parameters.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public ServerParameters ServerParameters
        {
            get
            {
                Contract.Ensures(Contract.Result<ServerParameters>() != null);

                return m_serverParameters;
            }
            set
            {
                Contract.Requires<ArgumentNullException>(value != null);

                m_serverParameters = value;
            }
        }

        /// <summary>
        /// The port on which to listen for incoming connection attempts.
        /// </summary>
        /// <remarks>Port must be between <see cref="F:System.Net.IPEndPoint.MinPort" /> and <see cref="F:System.Net.IPEndPoint.MaxPort" />.</remarks>
        [Description("Specifies the port on which to listen for incoming connection attempts.")]
        [DefaultValue(Constants.ConnectionPortDefaultValue)]
        [Category("Connection Server")]
        public int ConnectionPort
        {
            get
            {
                Contract.Ensures(Contract.Result<int>() >= IPEndPoint.MinPort && Contract.Result<int>() <= IPEndPoint.MaxPort);

                return m_serverParameters.ConnectionPort;
            }
            set
            {
                Contract.Requires<ArgumentOutOfRangeException>(value >= IPEndPoint.MinPort && value <= IPEndPoint.MaxPort);

                m_serverParameters.ConnectionPort = value;
            }
        }

        /// <summary>
        /// The current version of the client software.
        /// </summary>
        [Description("Specifies the current version of the client software.")]
        [DefaultValue(Constants.ClientVersionDefaultValue)]
        [Category("Connection Server")]
        public string ClientVersion
        {
            get
            {
                Contract.Ensures(!String.IsNullOrWhiteSpace(Contract.Result<string>()));

                return m_serverParameters.ClientVersion;
            }
            set
            {
                Contract.Requires<ArgumentNullException>(!String.IsNullOrWhiteSpace(value));

                m_serverParameters.ClientVersion = value;
            }
        }

        /// <summary>
        /// The address to use in order to update the client software..
        /// </summary>
        [Description("Specifies the address to use in order to update the client software.")]
        [DefaultValue(Constants.UpdateAddressDefaultValue)]
        [Category("Connection Server")]
        public string UpdateAddress
        {
            get
            {
                Contract.Ensures(!String.IsNullOrWhiteSpace(Contract.Result<string>()));

                return m_serverParameters.UpdateAddress;
            }
            set
            {
                Contract.Requires<ArgumentNullException>(!String.IsNullOrWhiteSpace(value));

                m_serverParameters.UpdateAddress = value;
            }
        }

        /// <summary>
        /// The port on which to listen for incoming connection attempts.
        /// </summary>
        /// <remarks>Port must be between <see cref="F:System.Net.IPEndPoint.MinPort" /> and <see cref="F:System.Net.IPEndPoint.MaxPort" />.</remarks>
        [Description("Specifies the port on which to listen for incoming connection attempts.")]
        [DefaultValue(Constants.ServerPortDefaultValue)]
        [Category("Server")]
        public int ServerPort
        {
            get
            {
                Contract.Ensures(Contract.Result<int>() >= IPEndPoint.MinPort && Contract.Result<int>() <= IPEndPoint.MaxPort);

                return m_serverParameters.ServerPort;
            }
            set
            {
                Contract.Requires<ArgumentOutOfRangeException>(value >= IPEndPoint.MinPort && value <= IPEndPoint.MaxPort);

                m_serverParameters.ServerPort = value;
            }
        }

        /// <summary>
        /// The maximum length of the pending connections queue.
        /// </summary>
        [Description("Specifies the maximum length of the pending connections queue.")]
        [DefaultValue(Constants.PendingConnectionsQueueLengthDefaultValue)]
        [Category("Server")]
        public int PendingConnectionsQueueLength
        {
            get
            {
                Contract.Ensures(Contract.Result<int>() >= 0);

                return m_serverParameters.PendingConnectionsQueueLength;
            }
            set
            {
                Contract.Requires<ArgumentOutOfRangeException>(value >= 0);
                
                m_serverParameters.PendingConnectionsQueueLength = value;
            }
        }

        /// <summary>
        /// The number of milliseconds during which the <see cref="T:Server.TcpServer" /> will be sleeping when it has no work to do.
        /// </summary>
        [Description("Specifies the number of milliseconds during which the server will be sleeping when it has no work to do.")]
        [DefaultValue(Constants.ServerIdleTimeDefaultValue)]
        [Category("Server")]
        public int ServerIdleTime
        {
            get
            {
                Contract.Ensures(Contract.Result<int>() >= 0);

                return m_serverParameters.ServerIdleTime;
            }
            set
            {
                Contract.Requires<ArgumentOutOfRangeException>(value >= 0);

                m_serverParameters.ServerIdleTime = value;
            }
        }

        /// <summary>
        /// How often (in milliseconds) the <see cref="T:Server.TcpServer" /> should verify that a client is still connected.
        /// </summary>
        [Description("How often (in milliseconds) the server should verify that a client is still connected.")]
        [DefaultValue(Constants.VerifyConnectionIntervalDefaultValue)]
        [Category("Server")]
        public int VerifyConnectionInterval
        {
            get
            {
                Contract.Ensures(Contract.Result<int>() >= 0);

                return m_serverParameters.VerifyConnectionInterval;
            }
            set
            {
                Contract.Requires<ArgumentOutOfRangeException>(value >= 0);

                m_serverParameters.VerifyConnectionInterval = value;
            }
        }

        /// <summary>
        /// The number of milliseconds to wait before the connection with a client is considered to be lost.
        /// </summary>
        [Description("Specifies the number of milliseconds to wait before the connection with a client is considered to be lost. (-1) to wait indefinitely.")]
        [DefaultValue(Constants.ClientTimeoutDefaultValue)]
        [Category("Client")]
        public int ClientTimeout
        {
            get
            {
                Contract.Ensures(Contract.Result<int>() >= 0);

                return m_serverParameters.ClientTimeout;
            }
            set
            {
                Contract.Requires<ArgumentOutOfRangeException>(value >= 0);

                m_serverParameters.ClientTimeout = value;
            }
        }

        /// <summary>
        /// The number of milliseconds during which the <see cref="T:Server.TcpClientManager" /> will be sleeping when it has no work to do.
        /// </summary>
        [Description("Specifies the number of milliseconds during which the client manager will be sleeping when it has no work to do.")]
        [DefaultValue(Constants.ClientManagerIdleTimeDefaultValue)]
        [Category("Client")]
        public int ClientManagerIdleTime
        {
            get
            {
                Contract.Ensures(Contract.Result<int>() >= 0);

                return m_serverParameters.ClientManagerIdleTime;
            }
            set
            {
                Contract.Requires<ArgumentOutOfRangeException>(value >= 0);

                m_serverParameters.ClientManagerIdleTime = value;
            }
        }

        /// <summary>
        /// The character encoding method.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public Encoding Encoding
        {
            get
            {
                Contract.Ensures(Contract.Result<Encoding>() != null);

                return m_serverParameters.Encoding;
            }
            set
            {
                Contract.Requires<ArgumentNullException>(value != null);

                m_serverParameters.Encoding = value;
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="T:Server.TcpServer" /> class.
        /// </summary>
        public TcpServer()
        {
            m_lock = new object();
            m_packetsToSend = new ConcurrentQueue<ClientPacket>();
            m_receivedPackets = new ConcurrentQueue<ClientPacket>();
            m_clients = new ConcurrentDictionary<TcpClientManager, bool>();
            m_pendingClientsAddresses = new ConcurrentBag<string>();
            m_serverParameters = new ServerParameters();
            InitializeComponent();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:Server.TcpServer" /> class with a provided <see cref="T:System.ComponentModel.IContainer" />.
        /// </summary>
        /// <param name="container">The <see cref="T:System.ComponentModel.IContainer" />  that contains the <see cref="T:System.ComponentModel.Component" />, if any, or null if the 
        /// <see cref="T:System.ComponentModel.Component" /> is not encapsulated in an <see cref="T:System.ComponentModel.IContainer" />.</param>
        public TcpServer(IContainer container)
            : this()
        {
            Contract.Requires<ArgumentNullException>(container != null);

            container.Add(this);
        }

        #endregion

        #region Methods

        /// <summary>
        /// Define the invariant valid state for this class.
        /// </summary>
        [ContractInvariantMethod]
        private void InvariantValidState()
        {
            Contract.Invariant(m_lock != null);
            Contract.Invariant(m_serverParameters != null);
            Contract.Invariant(m_packetsToSend != null);
            Contract.Invariant(m_receivedPackets != null);
            Contract.Invariant(m_clients != null);
            Contract.Invariant(m_pendingClientsAddresses != null);
        }


        /// <summary>
        /// Start the <see cref="T:Server.TcpServer" />.
        /// </summary>
        /// <exception cref="T:System.Net.NetworkInformation.NetworkInformationException">The call to the native <c>GetAdaptersAddresses</c> function failed.</exception>
        /// <exception cref="T:System.NotImplementedException">This method is not implemented on the platform. This method uses the native <c>NotifyStableUnicastIpAddressTable</c> function that is supported on Windows Vista and later.</exception>
        /// <exception cref="T:System.Security.SecurityException">The caller does not have necessary <see cref="F:System.Net.NetworkInformation.NetworkInformationAccess.Read" /> permission.</exception>
        /// <exception cref="T:System.ComponentModel.Win32Exception">The call to the native <c>NotifyStableUnicastIpAddressTable</c> function failed.</exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">The address family is <see cref="F:System.Net.Sockets.AddressFamily.InterNetworkV6" /> and the address is bad -or- An error occurred while accessing the socket.</exception>
        /// <exception cref="T:System.Collections.Generic.KeyNotFoundException">The <see cref="T:Server.AddressPacket" /> type was not mapped.</exception>
        /// <exception cref="T:System.InvalidOperationException">The underlying <see cref="T:System.Net.Sockets.Socket" /> is null.</exception>
        public void Start()
        {
            // prevent starting if server is already started
            if (m_isStarted) return;

            lock (m_lock)
            {
                // prevent concurrent start
                if (m_isStarted) return;

                // server is started
                m_isStarted = true;

                try
                {
                    // retrieve the IPV6 address
                    string ipv6Address = GetIpv6Address();

                    // start the connection server
                    m_connectionServer = new ConnectionServer(this, new ConnectionInformations(ipv6Address, m_serverParameters.ServerPort, m_serverParameters.ClientVersion, m_serverParameters.UpdateAddress),
                        m_serverParameters.ConnectionPort, m_serverParameters.PendingConnectionsQueueLength, m_serverParameters.ServerIdleTime, m_serverParameters.Encoding, OnConnectionServerError, OnConnectionServerClientConnected);
                    m_connectionServer.Start();

                    // start listening for incoming connection requests
                    m_clientListener = new TcpListener(IPAddress.IPv6Any, m_serverParameters.ServerPort);
                    m_clientListener.AllowNatTraversal(true);
                    m_clientListener.Start(m_serverParameters.PendingConnectionsQueueLength);
                }
                catch (SystemException e)
                {
                    HandleException(e);
                }

                // start the connection handler
                m_handleConnections = true;
                m_connectionsHandlerTask = Task.Factory.StartNew(ConnectionsHandler);

                // start the disconnection handler
                m_handleDisonnections = true;
                m_disconnectionsHandlerTask = Task.Factory.StartNew(DisconnectionsHandler);

                // start the receive packets handler
                m_handleReceivedPackets = true;
                m_receivePacketsHandlerTask = Task.Factory.StartNew(ReceivePacketsHandler);

                // start the send packets handler
                m_handlePacketsSending = true;
                m_sendPacketsHandlerTask = Task.Factory.StartNew(SendPacketsHandler);
            }
        }

        /// <summary>
        /// Get the server IPV6 address.
        /// </summary>
        /// <returns>The server IPV6 address.</returns>
        /// <exception cref="T:System.Net.NetworkInformation.NetworkInformationException">The call to the native <c>GetAdaptersAddresses</c> function failed.</exception>
        /// <exception cref="T:System.NotImplementedException">This method is not implemented on the platform. This method uses the native <c>NotifyStableUnicastIpAddressTable</c> function that is supported on Windows Vista and later.</exception>
        /// <exception cref="T:System.Security.SecurityException">The caller does not have necessary <see cref="F:System.Net.NetworkInformation.NetworkInformationAccess.Read" /> permission.</exception>
        /// <exception cref="T:System.ComponentModel.Win32Exception">The call to the native <c>NotifyStableUnicastIpAddressTable</c> function failed.</exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">The address family is <see cref="F:System.Net.Sockets.AddressFamily.InterNetworkV6" /> and the address is bad.</exception>
        private string GetIpv6Address()
        {
            Contract.Ensures(!String.IsNullOrWhiteSpace(Contract.Result<string>()));

            var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
            if (ipGlobalProperties != null)
            {
                var unicastAddresses = ipGlobalProperties.GetUnicastAddresses();
                if (unicastAddresses != null)
                {
                    var unicastAddress = unicastAddresses.FirstOrDefault(a => a.Address.IsIPv6Teredo);
                    if (unicastAddress != null)
                    {
                        var address = unicastAddress.Address;
                        if (address != null)
                        {
                            string result = address.ToString();
                            if (!String.IsNullOrWhiteSpace(result))
                                return result;
                        }
                    }
                }
            }
            throw new InvalidOperationException("IPV6 address could not be retrieved");
        }

        /// <summary>
        /// Stop the <see cref="T:Server.TcpServer" />.
        /// </summary>
        /// <exception cref="T:System.ArgumentOutOfRangeException">Port is not between <see cref="F:System.Net.IPEndPoint.MinPort" /> and <see cref="F:System.Net.IPEndPoint.MaxPort" /> - or - millisecondsTimeout is a negative number other than -1, which represents an infinite time-out.</exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">An error occurred while accessing the socket.</exception>
        /// <exception cref="T:System.ObjectDisposedException">A <see cref="T:System.Threading.Tasks.Task" /> has already been disposed.</exception>
        /// <exception cref="T:System.AggregateException">A <see cref="T:System.Threading.Tasks.Task" /> was canceled -or- an exception was thrown during the execution of the <see cref="T:System.Threading.Tasks.Task" />. If the task was canceled, the 
        /// <see cref="T:System.AggregateException" /> contains an <see cref="T:System.OperationCanceledException" /> in its <see cref="P:System.AggregateException.InnerExceptions" /> collection.</exception>
        public void Stop()
        {
            Contract.Assert(m_lock != null);
            Contract.Assert(m_clients != null);
            Contract.Assert(m_packetsToSend != null);
            Contract.Assert(m_receivedPackets != null);

            // prevent stopping if server is not started
            if (!m_isStarted) return;

            lock (m_lock)
            {
                // prevent concurrent stop
                if (!m_isStarted) return;

                // stop the handler tasks
                m_handleConnections = m_handleDisonnections = m_handleReceivedPackets = m_handlePacketsSending = false;
                try
                {
                    Parallel.Invoke(() =>
                    {
                        if (m_connectionServer != null)
                            m_connectionServer.Dispose();
                    }, () =>
                    {
                        if (m_connectionsHandlerTask.Wait(m_serverParameters.ServerIdleTime))
                            m_connectionsHandlerTask.Dispose();
                    }, () =>
                    {
                        if (m_disconnectionsHandlerTask.Wait(m_serverParameters.ServerIdleTime))
                            m_disconnectionsHandlerTask.Dispose();
                    }, () =>
                    {
                        if (m_receivePacketsHandlerTask.Wait(m_serverParameters.ServerIdleTime))
                            m_receivePacketsHandlerTask.Dispose();
                    }, () =>
                    {
                        if (m_sendPacketsHandlerTask.Wait(m_serverParameters.ServerIdleTime))
                            m_sendPacketsHandlerTask.Dispose();
                    });
                }
                catch (SystemException e)
                {
                    HandleException(e);
                }
                catch (AggregateException e)
                {
                    HandleException(e);
                }
                finally
                {
                    m_connectionServer = null;
                    m_connectionsHandlerTask = m_disconnectionsHandlerTask = m_receivePacketsHandlerTask = m_sendPacketsHandlerTask = null;

                    ClientPacket clientPacket;
                    while (m_receivedPackets.TryDequeue(out clientPacket)) { }
                    while (m_packetsToSend.TryDequeue(out clientPacket)) { }

                    // disconnect all clients
                    try
                    {
                        Parallel.ForEach(m_clients.Keys, client =>
                        {
                            client.Dispose();

                            // raise the OnTcpServerClientDisposed event if defined
                            if (OnTcpServerClientDisposed != null)
                                OnTcpServerClientDisposed(this, new TcpClientManagerEventArgs(client));
                        });
                    }
                    catch (ObjectDisposedException e)
                    {
                        HandleException(e);
                    }
                    catch (AggregateException e)
                    {
                        HandleException(e);
                    }
                    finally
                    {
                        m_clients.Clear();

                        // stop the client listener
                        try
                        {
                            if (m_clientListener != null)
                            {
                                m_clientListener.Stop();
                                m_clientListener = null;
                            }
                        }
                        catch (SocketException e)
                        {
                            HandleException(e);
                        }
                        finally
                        {
                            GC.Collect();

                            // server is stopped
                            m_isStarted = false;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Send a <see cref="T:Server.Packets.Packet" /> to a specified client.
        /// </summary>
        public void SendPacket(TcpClientManager client, Packet packet)
        {
            Contract.Requires<ArgumentNullException>(client != null);
            Contract.Requires<ArgumentNullException>(packet != null);
            Contract.Assert(m_packetsToSend != null);

            m_packetsToSend.Enqueue(new ClientPacket(client, packet));
        }

        /// <summary>
        /// Handle the incoming connections.
        /// </summary>
        /// <exception cref="T:System.InvalidOperationException">The listener has not been started with a call to <see cref="M:System.Net.Sockets.TcpListener.Start()" />.</exception>
        /// <exception cref="T:System.OverflowException">The dictionary already contains the maximum number of elements, <see cref="F:System.Int32.MaxValue" />.</exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">Use the <see cref="P:System.Net.Sockets.SocketException.ErrorCode" /> property to obtain the specific error code. When you have obtained this code, you can refer to the Windows Sockets version 2 API error code documentation in MSDN for a detailed description of the error.</exception>
        private void ConnectionsHandler()
        {
            Contract.Assert(m_serverParameters.ServerIdleTime >= 0);

            // loop while connections should be handled
            while (m_handleConnections)
            {
                try
                {
                    // there is a pending connection request
                    if (m_clientListener != null && m_clientListener.Pending())

                        // handle the new client
                        HandleClient(m_clientListener.AcceptTcpClient());

                    // wait until the next pending connection request check
                    else
                        Thread.Sleep(m_serverParameters.ServerIdleTime);
                }
                catch (Exception e)
                {
                    HandleException(e);
                }
            }
        }

        /// <summary>
        /// Handle a <see cref="T:System.Net.Sockets.TcpClient" />.
        /// </summary>
        /// <param name="client">The <see cref="T:System.Net.Sockets.TcpClient" />.</param>
        /// <exception cref="T:System.OverflowException">The dictionary already contains the maximum number of elements, <see cref="F:System.Int32.MaxValue" />.</exception>
        private void HandleClient(TcpClient client)
        {
            Contract.Requires<ArgumentNullException>(client != null);
            Contract.Assert(m_clients != null);

#warning IPV6 pas IPV4 !
            //if (m_pendingClientsAddresses.Contains(client.GetRemoteIp()))
            //{
            client.NoDelay = true;
            var manager = new TcpClientManager(this, client, m_serverParameters.ClientManagerIdleTime, m_serverParameters.ClientTimeout, m_serverParameters.Encoding, OnClientManagerError, OnPacketSent);
            m_clients.TryAdd(manager, true);

            // raise the OnTcpServerClientCreated event if defined, then start the TcpClientManager
            if (OnTcpServerClientCreated != null)
                Task.Factory.StartNew(() => OnTcpServerClientCreated(this, new TcpClientManagerEventArgs(manager)))
                    .ContinueWith(task => { manager.Start(); });
            //}
            //else
            //{
            //    client.Close();

            //    // raise the OnTcpServerError event if defined
            //    if (OnTcpServerError != null)
            //        OnTcpServerError(this, new ExceptionEventArgs(new InvalidOperationException("A client tried to connect without a prior connection to the ConnectionServer")));
            //}
            Thread.Yield();
        }

        /// <summary>
        /// Handle the disconnections.
        /// </summary>
        /// <exception cref="T:System.ObjectDisposedException">A <see cref="T:System.Threading.Tasks.Task" /> has already been disposed.</exception>
        /// <exception cref="T:System.AggregateException">A <see cref="T:System.Threading.Tasks.Task" /> was canceled -or- an exception was thrown during the execution of the <see cref="T:System.Threading.Tasks.Task" />. If the task was canceled, the 
        /// <see cref="T:System.AggregateException" /> contains an <see cref="T:System.OperationCanceledException" /> in its <see cref="P:System.AggregateException.InnerExceptions" /> collection.</exception>
        private void DisconnectionsHandler()
        {
            Contract.Assert(m_clients != null);

            // loop while disconnections should be handled
            while (m_handleDisonnections)
            {
                bool value;

                // iterate the clients
                Parallel.ForEach(m_clients.Keys, client =>
                {
                    if (!client.IsConnected)
                    {
                        m_clients.TryRemove(client, out value);

                        client.Dispose();

                        // raise the OnTcpServerClientDisposed event if defined
                        if (OnTcpServerClientDisposed != null)
                            Task.Factory.StartNew(() => OnTcpServerClientDisposed(this, new TcpClientManagerEventArgs(client)));
                    }
                });

                // wait until the next clients connection check
                Thread.Sleep(m_serverParameters.VerifyConnectionInterval);
            }
        }

        /// <summary>
        /// Handle the received <see cref="T:Server.Packets.Packet" />.
        /// </summary>
        private void ReceivePacketsHandler()
        {
            Contract.Assert(m_receivedPackets != null);

            // loop while packet receiving should be handled
            while (m_handleReceivedPackets)
            {
                ClientPacket clientPacket;
                if (m_receivedPackets.TryDequeue(out clientPacket))
                {
                    // raise the OnPacketReceived event if defined
                    if (OnPacketReceived != null)
                        Task.Factory.StartNew(() => OnPacketReceived(clientPacket.Client, new PacketEventArgs(clientPacket.Packet)));
                    Thread.Yield();
                }
                else
                    Thread.Sleep(m_serverParameters.ServerIdleTime);
            }
        }

        /// <summary>
        /// Handle the send of the <see cref="T:Server.Packets.Packet" /> to send.
        /// </summary>
        private void SendPacketsHandler()
        {
            Contract.Assert(m_packetsToSend != null);

            // loop while packet sending should be handled
            while (m_handlePacketsSending)
            {
                ClientPacket clientPacket;
                if (m_packetsToSend.TryDequeue(out clientPacket))
                {
                    clientPacket.Client.PacketsToSend.Enqueue(clientPacket.Packet);
                    Thread.Yield();
                }
                else
                    Thread.Sleep(m_serverParameters.ServerIdleTime);
            }
        }

        /// <summary>
        /// Handle an <see cref="T:System.Exception" />.
        /// </summary>
        /// <param name="e">The <see cref="T:System.Exception" /> that occured.</param>
        private void HandleException(Exception e)
        {
            // raise the OnTcpServerError event if defined
            if (OnTcpServerError == null)
                throw e;
            Task.Factory.StartNew(() => OnTcpServerError(this, new ExceptionEventArgs(e)));
            Thread.Yield();
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <param name="disposing"><c>true</c> if managed resources should be disposed; otherwise <c>false</c>.</param>
        protected override void Dispose(bool disposing)
        {
            // the finalizer didn't calls for this
            if (disposing)
            {
                if (components != null)
                    components.Dispose();
                Stop();
            }
            base.Dispose(disposing);
        }

        #endregion
    }
}
